package com.jinly.ryder.flow.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jinly.ryder.account.service.UserAuthService;
import com.jinly.ryder.account.service.model.UserAuth;
import com.jinly.ryder.common.auth.model.AuthRole;
import com.jinly.ryder.common.auth.model.AuthType;
import com.jinly.ryder.common.error.SystemErrorType;
import com.jinly.ryder.common.utils.SpringContextUtil;
import com.jinly.ryder.flow.dao.FlowApplyMapper;
import com.jinly.ryder.flow.dao.FlowAuditMapper;
import com.jinly.ryder.flow.dao.entity.FlowApplyDO;
import com.jinly.ryder.flow.dao.entity.FlowAuditDO;
import com.jinly.ryder.flow.service.FlowApplyAuditService;
import com.jinly.ryder.flow.service.FlowApplyQueryService;
import com.jinly.ryder.flow.service.FlowRuleService;
import com.jinly.ryder.flow.service.mapping.FlowApplyMapping;
import com.jinly.ryder.flow.service.mapping.FlowAuditMapping;
import com.jinly.ryder.flow.service.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class FlowApplyAuditServiceImpl implements FlowApplyAuditService {
    private final FlowApplyMapper flowApplyMapper;
    private final FlowAuditMapper flowAuditMapper;
    private final FlowApplyMapping flowApplyMapping;
    private final FlowAuditMapping flowAuditMapping;
    private final FlowApplyQueryService flowApplyQueryService;
    private final FlowRuleService flowRuleService;
    private final UserAuthService userAuthService;

    /**
     * 提交申请
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitApply(FlowApply flowApply) {
        Assert.isTrue(flowApply.getAppName() != null, "应用不能为空");
        FlowRule flowRule = flowRuleService.getRuleByName(flowApply.getFlowName());
        if (flowRule == null) SystemErrorType.INTERNAL_SERVER_ERROR.throwError("申请不存在");
        flowApply.setFlowRuleId(flowRule.getId());
        flowApply.setApplyStatus(ApplyStatus.SUBMIT);
        FlowApplyDO flowApplyDO = flowApplyMapping.toEntity(flowApply);
        flowApplyMapper.insert(flowApplyDO);
        flowApply.setId(flowApplyDO.getId());
        auditInvoke(flowApply, null, flowRule);
        nextAudit(flowApply, null, flowRule);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelApply(Long applyId, Long userId) {
        FlowApply flowApply = flowApplyQueryService.applyDetail(applyId);
        if (flowApply != null || Objects.equals(userId, flowApply.getApplyUserId())) {
            closeFlowApply(applyId, null);
            changeApplyStatus(flowApply, ApplyStatus.CANCEL);
            auditInvoke(flowApply, null, flowRuleService.getRuleByName(flowApply.getFlowName()));
        }
    }


    /**
     * 修改申请状态
     */
    private void changeApplyStatus(FlowApply flowApply, ApplyStatus status) {
        if (flowApply != null) {
            flowApply.setApplyStatus(status);
            flowApplyMapper.updateById(flowApplyMapping.toEntity(flowApply));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitAudit(FlowAudit flowAudit) {
        if (flowAudit.getAuditResult() == AuditResult.WAITING) return;
        FlowApply flowApply = flowApplyQueryService.applyDetail(flowAudit.getApplyId());
        if (flowApply != null) {
            FlowAudit flowAuditData = flowApplyQueryService.auditDetail(flowAudit.getId());
            if (flowAuditData != null && Objects.equals(flowAuditData.getAuditUserId(), flowAudit.getAuditUserId())) {
                FlowRule flowRule = flowRuleService.getRule(flowApply.getFlowRuleId());
                flowAuditData.setAuditResult(flowAudit.getAuditResult());
                flowAuditData.setRemark(flowAudit.getRemark());
                flowAuditMapper.updateById(flowAuditMapping.toEntity(flowAuditData));
                auditInvoke(flowApply, flowAuditData, flowRule);
                //关闭其他审核
                closeFlowApply(flowAudit.getApplyId(), flowAudit.getFlowStepName());
                nextAudit(flowApply, flowAuditData, flowRule);
            }
        }
    }

    private void auditInvoke(FlowApply flowApply, FlowAudit flowAudit, FlowRule flowRule) {
        if (flowRule.getInvokeInterface() != null && flowRule.getInvokeMethod() != null) {
            boolean needInvoke = true;
            if (flowApply.getApplyStatus() == ApplyStatus.AUDITING) {
                FlowStep flowStep = flowRule.getStep(flowAudit.getFlowStepName());
                needInvoke = flowStep.getNeedInvoke();
            }
            if (needInvoke) {
                Object bean = SpringContextUtil.getBean(flowRule.getInvokeInterface());
                Method method = ReflectionUtils.findMethod(AopUtils.getTargetClass(bean), flowRule.getInvokeMethod(), FlowApply.class, FlowAudit.class);
                if (method != null) {
                    ReflectionUtils.invokeMethod(method, bean, flowApply, flowAudit);
                }
            }
        }
    }

    private void closeFlowApply(Long applyId, String flowStepName) {
        Wrapper<FlowAuditDO> queryWrapper = new QueryWrapper<FlowAuditDO>().lambda()
                .eq(FlowAuditDO::getApplyId, applyId)
                .eq(flowStepName != null, FlowAuditDO::getFlowStepName, flowStepName)
                .eq(FlowAuditDO::getAuditResult, AuditResult.WAITING);
        flowAuditMapper.selectList(queryWrapper).forEach(a ->
                flowAuditMapper.deleteById(a.getId())
        );
    }

    /**
     * 流转下个审核点
     */
    private void nextAudit(FlowApply flowApply, FlowAudit currFlowAudit, FlowRule flowRule) {
        FlowStep nextStep = null;
        if (flowApply.getApplyStatus() == ApplyStatus.SUBMIT) {
            nextStep = flowRule.getFirstStep();
            currFlowAudit = new FlowAudit();
            currFlowAudit.setAuditUserId(flowApply.getApplyUserId());
            currFlowAudit.setFlowStepName("提交");
            currFlowAudit.setAuditResult(AuditResult.APPROVE);
            changeApplyStatus(flowApply, ApplyStatus.AUDITING);
        } else if (flowApply.getApplyStatus() == ApplyStatus.AUDITING) {
            FlowStep currStep = flowRule.getStep(currFlowAudit.getFlowStepName());
            String nextStepName = currStep.getNextStepMap().get(currFlowAudit.getAuditResult().getCode());
            nextStep = flowRule.getStep(nextStepName);
        }

        if (nextStep == null) { //流程结束
            changeApplyStatus(flowApply, currFlowAudit.getAuditResult() == AuditResult.APPROVE ? ApplyStatus.APPROVE : ApplyStatus.REJECT);
            auditInvoke(flowApply, currFlowAudit, flowRule);
        } else {//下个流程审核
            Set<Long> auditUserIds = getAuditUser(flowApply, nextStep);
            //如果下一个审批人跟当前审批人相同 则跳过
            if (auditUserIds.contains(currFlowAudit.getAuditUserId())) {
                currFlowAudit.setFlowStepName(nextStep.getName());
                nextAudit(flowApply, currFlowAudit, flowRule);
            } else {
                for (Long auditUserId : auditUserIds) {
                    addAudit(flowApply.getId(), nextStep.getName(), auditUserId, AuditResult.WAITING);
                }
            }
        }
    }


    private FlowAudit addAudit(Long applyId, String stepName, Long auditUserId, AuditResult auditResult) {
        FlowAudit flowAudit = new FlowAudit();
        flowAudit.setApplyId(applyId);
        flowAudit.setFlowStepName(stepName);
        flowAudit.setAuditUserId(auditUserId);
        flowAudit.setAuditResult(auditResult);
        FlowAuditDO flowAuditDO = flowAuditMapping.toEntity(flowAudit);
        flowAuditMapper.insert(flowAuditDO);
        flowAudit.setId(flowAuditDO.getId());
        return flowAudit;

    }

    private Set<Long> getAuditUser(FlowApply flowApply, FlowStep nextStep) {
        List<UserAuth> list = new ArrayList<>();
        for (FlowStep.AuditUserCondition auditUserCondition : nextStep.getAuditCondition().getUserConditionList()) {
            String authData = null;
            if (auditUserCondition.getAuthType() == AuthType.APP) {
                authData = flowApply.getAppName();
            }
            list = userAuthService.queryUserAuthList(auditUserCondition.getAuthType(), authData, auditUserCondition.getAuthRole());
        }
        if (list.size() == 0) {
            list = userAuthService.queryUserAuthList(AuthType.MANAGE, null, AuthRole.ADMIN);
        }
        return list.stream().map(UserAuth::getUserId).collect(Collectors.toSet());
    }
}
